﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;

using zijian666.AnyExtensions.Privates;

namespace zijian666.AnyExtensions;

/// <summary>
/// 待查询对象 <seealso cref="IQueryable"/> 相关的扩展方法
/// </summary>
public static class QueryableExtensions
{
    /// <summary>
    /// 创建表达式树构建程序 <seealso cref="LinqExpressionBuilder{TSource}"/>
    /// </summary>
    public static LinqExpressionBuilder<TSource> LinqBuidler<TSource>(this IQueryable<TSource> source)
        => new();

    /// <summary>
    /// 确定序列为空
    /// </summary>
    public static bool IsNullOrEmpty<TSource>([NotNullWhen(false)] this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
      => source is null || !source.Any(predicate);

    /// <summary>
    /// 返回 <seealso cref="HashSet{TSource}"/>
    /// </summary>
    public static HashSet<TResult> ToHashSet<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> valueSelector, IEqualityComparer<TResult>? comparer = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }

        return source is null ? new HashSet<TResult>(comparer) : source.Select(valueSelector).ToHashSet(comparer);
    }


    /// <summary>
    /// 返回 <seealso cref="HashSet{string}"/>
    /// </summary>
    public static HashSet<string> ToHashSet<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> valueSelector, CompareOptions options, CultureInfo? culture = null)
    {
        if (valueSelector is null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }
        var comparer = culture.GetStringComparer(options);
        return ToHashSet(source, valueSelector, comparer);
    }

    /// <summary>
    /// 构建 where ${<paramref name="columnName"/>} in ( ${<paramref name="values"/>} ) 语句查询数据库
    /// </summary>
    public static IQueryable<TSource> FindByIn<TSource, TKey>(this IQueryable<TSource> source, string columnName, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        var builder = new LinqExpressionBuilder<TSource>();

        var whereIn = builder.Equal(columnName, values);

        return source.Where(whereIn);
    }

    /// <summary>
    /// 构建 where ${<paramref name="columnName"/>} in ( ${<paramref name="values"/>} ) 语句查询数据库
    /// </summary>
    public static IQueryable<TSource> FindByIn<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }

        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var builder = new LinqExpressionBuilder<TSource>();
        var whereIn = builder.Equal(selector, values);
        return source.Where(whereIn);
    }

    /// <summary>
    /// 构建 where Id in ( ${<paramref name="values"/>} ) 语句查询数据库
    /// </summary>
    public static IQueryable<TSource> FindByIds<TSource, TId>(this IQueryable<TSource> source, IEnumerable<TId> ids)
        => FindByIn(source, "Id", ids);


    /// <summary>
    /// 构建 where ${<paramref name="columnName"/>} not in ( ${<paramref name="values"/>} ) 语句查询数据库
    /// </summary>
    public static IQueryable<TSource> FindByNotIn<TSource, TKey>(this IQueryable<TSource> source, string columnName, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        var builder = new LinqExpressionBuilder<TSource>();

        var whereIn = builder.NotEqual(columnName, values);

        return source.Where(whereIn);
    }

    /// <summary>
    /// 构建 where ${<paramref name="columnName"/>} not in ( ${<paramref name="values"/>} ) 语句查询数据库
    /// </summary>
    public static IQueryable<TSource> FindByNotIn<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, IEnumerable<TKey> values)
    {
        if (source is null || values is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }

        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var builder = new LinqExpressionBuilder<TSource>();
        var whereIn = builder.NotEqual(selector, values);
        return source.Where(whereIn);
    }



    /// <summary>
    /// 排除删除标识为 1 的数据，（构建 where IsDelete = 0 语句查询数据库），如果实体类不存在 <paramref name="columnName"/> ，则该方法不做任何操作
    /// </summary>
    public static IQueryable<TSource> WithoutDeleted<TSource>(this IQueryable<TSource> source, string columnName = "IsDelete")
    {
        if (typeof(TSource).GetProperty(columnName) is not null)
        {
            var builder = new LinqExpressionBuilder<TSource>();
            var predicate = builder.NotEqual(columnName, true);
            return source.Where(predicate);
        }
        return source;
    }

    /// <summary>
    /// where ${<paramref name="columnName"/>} between ${<paramref name="begin"/>} and ${<paramref name="end"/>} 语句查询数据库
    /// </summary>
    /// <remarks>如果 <paramref name="begin"/> 或 <paramref name="end"/> 为空, 会自动处理为大于或小于操作</remarks>
    /// <returns></returns>
    public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, string columnName, TKey begin, TKey end)
        where TKey : IComparable
    {
        var builder = new LinqExpressionBuilder<TSource>();
        var predicate = builder.Between(columnName, begin, end);
        return source.Where(predicate);
    }

    /// <summary>
    /// where ${<paramref name="columnName"/>} between ${<paramref name="begin"/>} and ${<paramref name="end"/>} 语句查询数据库
    /// </summary>
    /// <remarks>如果 <paramref name="begin"/> 或 <paramref name="end"/> 为空, 会自动处理为大于或小于操作</remarks>
    /// <returns></returns>
    public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, string columnName, TKey? begin, TKey? end)
        where TKey : struct, IComparable
    {
        var builder = new LinqExpressionBuilder<TSource>();
        var predicate = builder.Between(columnName, begin, end);
        return source.Where(predicate);
    }


    /// <summary>
    /// where ${<paramref name="selector"/>} between ${<paramref name="begin"/>} and ${<paramref name="end"/>} 语句查询数据库
    /// </summary>
    /// <remarks>如果 <paramref name="begin"/> 或 <paramref name="end"/> 为空, 会自动处理为大于或小于操作</remarks>
    /// <exception cref="ArgumentNullException"></exception>
    public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, TKey begin, TKey end)
        where TKey : IComparable
    {
        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var builder = new LinqExpressionBuilder<TSource>();
        var predicate = builder.Between(selector, begin, end);
        return source.Where(predicate);
    }

    /// <summary>
    /// where ${<paramref name="selector"/>} between ${<paramref name="begin"/>} and ${<paramref name="end"/>} 语句查询数据库
    /// </summary>
    /// <remarks>如果 <paramref name="begin"/> 或 <paramref name="end"/> 为空, 会自动处理为大于或小于操作</remarks>
    /// <exception cref="ArgumentNullException"></exception>
    public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> selector, TKey? begin, TKey? end)
        where TKey : struct, IComparable
    {
        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var builder = new LinqExpressionBuilder<TSource>();
        var predicate = builder.Between(selector, begin, end);
        return source.Where(predicate);
    }


    /// <summary>
    /// where ${<paramref name="selector"/>} between ${<paramref name="begin"/>} and ${<paramref name="end"/>} 语句查询数据库
    /// </summary>
    /// <remarks>如果 <paramref name="begin"/> 或 <paramref name="end"/> 为空, 会自动处理为大于或小于操作</remarks>
    /// <exception cref="ArgumentNullException"></exception>
    public static IQueryable<TSource> Between<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey?>> selector, TKey? begin, TKey? end)
        where TKey : struct, IComparable
    {
        if (selector is null)
        {
            throw new ArgumentNullException(nameof(selector));
        }

        var builder = new LinqExpressionBuilder<TSource>();
        var predicate = builder.Between(selector, begin, end);
        return source.Where(predicate);
    }

    private static bool IsAsc(string? orderby)
    {
        orderby = orderby?.Trim();
        if (string.IsNullOrEmpty(orderby) || "asc".Equals(orderby, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }
        if ("desc".Equals(orderby, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }
        throw new FormatException("排序标识有误，仅支持desc或acs");
    }

    /// <summary>
    /// 对序列的元素进行排序。
    /// </summary>
    public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string? expr)
    {
        var orderbys = expr?.Split<string>(',').WithoutWhiteSpace().ToArray();
        if (orderbys.IsNullOrEmpty())
        {
            return (IOrderedQueryable<TSource>)source;
        }
        var orderby = orderbys[0].Trim().Split(' ');
        var ordered = source.OrderBy(orderby[0], IsAsc(orderby.ElementAtOrDefault(1)));

        for (var i = 1; i < orderbys.Length; i++)
        {
            orderby = orderbys[i].Trim().Split(' ');
            ordered = ordered.ThenBy(orderby[0], IsAsc(orderby.ElementAtOrDefault(1)));
        }

        return ordered;
    }

    /// <summary>
    /// 对序列的元素进行排序。
    /// </summary>
    public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string name, bool asc)
    {
        var builder = new LinqExpressionBuilder<TSource>();
        var keySelector = builder.Property(name);

        var method = asc ? Methods.Queryable_OrderBy : Methods.Queryable_OrderByDescending;
        method = method.MakeGenericMethod(typeof(TSource), keySelector.ReturnType);

        return (IOrderedQueryable<TSource>)method.Invoke(null, [source, keySelector])!;
    }

    /// <summary>
    /// 对序列的元素进行排序。
    /// </summary>
    public static IOrderedQueryable<TSource> ThenBy<TSource>(this IOrderedQueryable<TSource> source, string name, bool asc)
    {
        var builder = new LinqExpressionBuilder<TSource>();
        var keySelector = builder.Property(name);

        var method = asc ? Methods.Queryable_ThenBy : Methods.Queryable_ThenByDescending;
        method = method.MakeGenericMethod(typeof(TSource), keySelector.ReturnType);

        return (IOrderedQueryable<TSource>)method.Invoke(null, [source, keySelector])!;
    }

    /// <summary>
    /// 判断条件后确定是否执行查询表达式
    /// </summary>
    public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool condition, Expression<Func<TSource, bool>>? truePredicate, Expression<Func<TSource, bool>>? falsePredicate = null)
    {
        if (source is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        var predicate = condition ? truePredicate : falsePredicate;

        if (predicate is null)
        {
            return source;
        }
        return source.Where(predicate);
    }

    /// <summary>
    /// 判断条件后确定是否执行查询操作
    /// </summary>
    public static IQueryable<TSource> If<TSource>(this IQueryable<TSource> source, bool condition, Func<IQueryable<TSource>, IQueryable<TSource>>? truthy, Func<IQueryable<TSource>, IQueryable<TSource>>? falsy = null)
    {
        if (source is null)
        {
            return GenericHelper<TSource>.EmptyQueryable;
        }
        return ObjectExtensions.If(source, condition, truthy, falsy) ?? GenericHelper<TSource>.EmptyQueryable;
    }
}